// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#include "StandinProxyContainer.h"
#include "GameFramework/WorldSettings.h"

#if WITH_EDITOR
#include "Misc/ConfigCacheIni.h"
#include "Serialization/ArchiveObjectCrc32.h"
#endif
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "Materials/MaterialInstance.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Engine/Texture.h"
#include "Engine/StaticMesh.h"
#include "PhysicsEngine/BodySetup.h"
#include "LevelUtils.h"
#include "Engine/LevelStreaming.h"
#include "Math/UnrealMathUtility.h"

#if WITH_EDITOR

void UStandinProxyContainer::SetMap(const UWorld* InMap)
{
	// Level should only be set once
	check(OwningMap.IsNull());

	OwningMap = InMap;
}

TSoftObjectPtr<UWorld> UStandinProxyContainer::GetMap() const
{
	return OwningMap;
}

void UStandinProxyContainer::AddStandinProxy(AStandInMeshActor* InStandinActor, UStaticMesh* InStaticMesh, UMaterialInterface* InMaterail, const TArray<UTexture2D*>& InTextures, const FName& InKey)
{
		
	//match with key
	FStandinProxyEntry* ExistingStandinEntry = Standins.FindByPredicate([InStandinActor, InKey](const FStandinProxyEntry& InEntry) 
		{
			TLazyObjectPtr<AStandInMeshActor> StandInActor = InEntry.GetStandinActor();
			
			if (!StandInActor.IsValid() || !StandInActor.Get())
			{
				return false;
			}
			
			return StandInActor->GetName() == InStandinActor->GetName();
		});

	if (ExistingStandinEntry)
	{
		ExistingStandinEntry->Update(InStaticMesh, InMaterail, InTextures, InKey);
	}
	else
	{
		FStandinProxyEntry NewStandinEntry(InStandinActor, InStaticMesh, InMaterail, InTextures, InKey);
		InStandinActor->Proxy = this;
		Standins.AddUnique(NewStandinEntry);
	}

}

void UStandinProxyContainer::RemoveActorMesh(AStandInMeshActor* InStandinActor)
{
	InStandinActor->Proxy = nullptr;

	FStandinProxyEntry* ExistingStandinEntry = Standins.FindByPredicate([InStandinActor](const FStandinProxyEntry& InEntry)
		{ 
			TLazyObjectPtr<AStandInMeshActor> StandInActor = InEntry.GetStandinActor();
			
			if (!StandInActor.IsValid() || !StandInActor.Get())
			{
				return false;
			}

			return StandInActor->GetName() == InStandinActor->GetName(); 
		});

	if (ExistingStandinEntry)
	{
		
		//clear standalone flag
		ExistingStandinEntry->MarkTransient();
		Standins.RemoveAll([ExistingStandinEntry](const FStandinProxyEntry& InEntry) { return *ExistingStandinEntry == InEntry; });
		this->MarkPackageDirty();
		
	}
}

void UStandinProxyContainer::GetUObjectInUseByStandinActor(AStandInMeshActor* InStandinActor,TArray<UObject*>& ExportToOwnPackage)
{
	FStandinProxyEntry* ExistingStandinEntry = Standins.FindByPredicate([InStandinActor](const FStandinProxyEntry& InEntry) 
		{
			TLazyObjectPtr<AStandInMeshActor> StandInActor = InEntry.GetStandinActor();
			
			if (!StandInActor.IsValid() || !StandInActor.Get())
			{
				return false;
			}

			return StandInActor->GetName() == InStandinActor->GetName();
		});

	if (ExistingStandinEntry)
	{
		auto StaticMesh = ExistingStandinEntry->GetStaticMesh();
		if (StaticMesh)
			ExportToOwnPackage.Add(StaticMesh);
		auto Material = ExistingStandinEntry->GetMaterial();
		if (Material)
			ExportToOwnPackage.Add(Material);

		auto Textures = ExistingStandinEntry->GetTextures();
		for (auto& Texture : Textures)
		{
			ExportToOwnPackage.Add(Texture);
		}

	}

}

void UStandinProxyContainer::PostLoad()
{
	Super::PostLoad();

	// Make sure proxy entries (UStandinProxyContainer::Standins) contain valid stand-in actors
	// after de-serialization, otherwise delete affected proxy entries during PostLoad.
	Clean();
}

void UStandinProxyContainer::Clean()
{
	// The level we reference must be loaded for us to know how to clean this package.
	// If it's not loaded, skip clean.
	if (OwningMap.IsNull() || OwningMap.ToSoftObjectPath().ResolveObject() == nullptr)
	{
		return;
	}

	// Remove all entries that have invalid actors
	Standins.RemoveAll([](const FStandinProxyEntry& InProxyMesh)
	{ 
		TLazyObjectPtr<AStandInMeshActor> StandInActor = InProxyMesh.GetStandinActor();

		// Invalid actor means that it has been deleted so we shouldnt hold onto its data
		if(!StandInActor.IsValid())
		{
			return true;
		}
		else if(StandInActor.Get()->Proxy == nullptr)
		{
			// No proxy means we are also invalid
			return true;
		}
		else if(!StandInActor.Get()->Proxy->ContainsDataForActor(StandInActor.Get()))
		{
			// actor and proxy are valid, but key differs (unbuilt)
			return true;
		}

		return false;
	});
}

#endif // #if WITH_EDITOR

#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)

bool UStandinProxyContainer::ContainsDataForActor(const AStandInMeshActor* InStandinActor) const
{
#if WITH_EDITOR
	FName Key;

	// Only re-generate the key in non-PIE worlds
	if(InStandinActor->GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor))
	{
		Key = InStandinActor->GetBuildKey();
	}
	else
	{
		Key = InStandinActor->GenerateBuildKey();
	}

	if(Key == NAME_None)
	{
		return false;
	}

	for(const FStandinProxyEntry& ProxyMesh : Standins)
	{
		if(ProxyMesh.GetKey() == Key)
		{
			return true;
		}
	}
#endif
	return false;
}

#endif
